與靜態反逆向技術相比,動態的更加難以反制。一直阻擋 Debugger 的行動與各種花樣百出的技術是分析時的噩夢。它的目標是隱藏和保護主要的代碼,通常被反逆向技術偵測到後,干擾行為也會開始干擾反編譯器。
這邊來講講一些常見的動態反逆向技術。
此次的程式與原始碼都放在 :
https://github.com/Dinlon5566/IT_Reverse_Engineering/tree/main/Dx29
很簡單直白的反逆向方法,主要就是檢查某段代碼的執行時間來判斷。若在其中有出現反編譯行為,就會使指令執行的時間比正常執行時間長多了。

首先這是最簡單的演示。透過 clock( ) 來取得自 CRT 初始化以來所消耗的時間。
那程式的話就用 loop 來代替一般的程式。
#include <iostream>
#include <Windows.h>
#include <time.h>
int main()
{
clock_t start, finish;
printf("Clock start\n");
start = clock();
for (int i = 0; i < INT_MAX; i++) {}
printf("Clock Stop\n");
finish = clock();
printf("%lf\n",double (finish - start)/CLOCKS_PER_SEC);
MessageBox(0,L"",L"",0);
}
先嘗試一般執行看看 :

花了大概三秒多。
此時利用 x64dbg 來執行,在 loop 段稍微暫停一下後再繼續 :

可以看到比一般執行長多了。
但是這樣的計時太不精準,於是通常會使用下面這個方法。
與 clock( ) 相比,RDTSC 直接用了 CPU 對於每個 Clock Cycle 進行記數,這樣可以計算出從計時點A 到計時點B 的每一個 tick。可以利用組語的 RDTSC 指令來取得 TSC ( Time Stamp Counter ) 中的時間。
這邊解釋一下 RDTSC 指令 : 時間計數器在64位元的 MSR 中,這個指令會把高 32 位元存在 EDX,低 32 位元放在 EAX。
除了使用組合語言指令,也有 C 上的
__rdtsc可以使用。
https://learn.microsoft.com/zh-tw/cpp/intrinsics/rdtsc?view=msvc-170
這邊展示用 __rdtsc( ) 與組合語言的用法,這兩個 return 的東西是一樣的。
UINT64 rdtsc() {
UINT64 res;
res = __rdtsc();
return res;
}
UINT64 AsmRdtsc() {
UINT64 res;
UINT32 highByte,lowByte;
__asm {
RDTSC
mov highByte,edx
mov lowByte, eax
}
res = highByte;
res <<= 0x20;
res |= lowByte;
return res;
}
這邊做一個直接取用 __rdtsc() 來計時的偵測器。
int main() {
UINT64 T1 = __rdtsc();
for (int i = 0; i < 50; i++) {}
UINT64 T2 = __rdtsc();
UINT64 delta = T2 - T1;
printf("Time = %lld\n",delta);
if (delta > 0xFFFF) {
printf("OUT!\n");
system("pause");
return 0;
}
printf("SAVE~ : )\n");
system("pause");
return 0;
}
來看三種情況 :

BP )

可以看到在不同情況下時間會在不同的區間,可以依據區間來判斷該程式是否被逆向分析中。
而這類破解的方法有 :
kernel 驅動程式直接廢掉 RDTSC。雖然要查出並破解這份範例程式的難度不難,但實際上這個技術會穿插在程式的各處,導致判斷起來非常困難。
一般的程序若是出現了異常的時候,會透過 SEH 的機制使 OS 接收到例外中斷,然後調用終止處理常式處裡。但是程序若運行在 Deubgger 下,例外將會傳給 Debugger 的 例外狀況處理常式( 類似用 try 包住整份程式 );透過這個特徵可以用來判斷程序是正常運行還是在反編譯環境。
SEH ( 結構例外狀況處裡 ) 說明手冊
https://learn.microsoft.com/zh-tw/cpp/cpp/structured-exception-handling-c-cpp?view=msvc-170
利用GetExceptionCode取的的例外狀況代碼
https://learn.microsoft.com/zh-tw/windows/win32/debug/getexceptioncode
而 EXECEPTION_BREAKPOINT ( 中斷點例外 ) 是在我們進行 Debugger 中很常使用的功能,當我們按下反編譯器旁邊的紅點,或是在 x64bdg 上停在某一個點的時候都會把到這樣的例外。
這邊演示若在程式碼中插入 0xCC 這個之前在 API Hook 中使用的 INT3 斷點,反編譯器將會停止在該位置。
printf("STOP\n");
__asm {
int 0x03
}
printf("Continue\n");
在 x32dbg 按下 F9,就算沒有設定 BP 也會停在 0x40104E 位置。而正常開啟的話則會直接退出不顯示"Continue"。

那,要甚麼透過這個方法來判斷是否有 Debugger 跑過呢 ? 其實很簡單,在異常前後設置可以接收例外的指令,當例外觸發後的例外接收器接有接收到的例外則是正常狀況;若是例外接收器沒有接到例外就可以判斷是運行在 Debugger ( 畢竟正常狀況下例外不會自己解決 )。

在 C++ 中可以利用 try、except、finally 關鍵字來接收例外,而組合語言上可以利用這幾行來設置
PUSH Handler // 設定的處裡位置
PUSH DWORD PTR FS:[0]
MOV DWORD PTR FS:[0],ESP
並且需要在程序中止前刪除掉 SEH
POP DWORD PTR FS:[0]
ADD ESP, 4
那來試試看應用在 C++ 上面吧 ,重點看在 __asm 中的處裡方式吧
#include <iostream>
#include "windows.h"
void debugerFound() {
MessageBox(0, L"Debuger Found", L"Excption_SEH", 0);
}
int main()
{
printf("Start");
__asm {
// 設置 SEH
push handler
push DWORD PTR fs : [0]
mov DWORD PTR fs:[0], esp
// 觸發 INT3,應該要直接跳到 handler 來處理異常
int 0x03
// 若是異常被處裡就會跑到這裡
call debugerFound
mov eax,0xFFFFFFFF // 直接讓你非法存取
jmp eax
handler:
// 處裡異常
mov eax, dword ptr ss : [esp + 0xc]
mov ebx, normal_code
mov dword ptr ds : [eax + 0xb8] , ebx
xor eax, eax
retn
normal_code :
// 拔除 SEH
pop dword ptr fs : [0]
add esp, 4
}
MessageBox(0, L"Save", L"Excption_SEH", 0);
}
利用 ollyDbg ( 因為 x64dbg 會自動把例外接收後又把例外放回去 ) 來執行這份程式,會發現中斷點被觸發後被跳到了非法存取 ( EIP = 0xFFFFFFFF )。

今天就到這邊,明天帶給大家關於陷阱標誌的反逆向技術。